package org.mozhu.mboot.core.data.hibernate.type;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.TextType;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;

@SuppressWarnings({"rawtypes", "unchecked"})
public class CollectionToStringType implements UserType, ParameterizedType, Serializable {

    private static final long serialVersionUID = -6071455010375290825L;
    private static final Logger logger = LoggerFactory.getLogger(CollectionToStringType.class);

    public static final String ELEMENT_TYPE = "elementType";
    public static final String COLLECTION_TYPE = "collectionType";
    public static final String SEPARATOR = "separator";

    private static final String DEFAULT_SEPARATOR = ";";
    private static final Class<?> DEFAULT_COLLECTION_TYPE = ArrayList.class;
    private static final Class<?> DEFAULT_ELEMENT_TYPE = String.class;
    /**
     * 默认;
     */
    private String separator = DEFAULT_SEPARATOR;
    /**
     * 默认 java.lang.String
     */
    private Class<?> elementClass = DEFAULT_ELEMENT_TYPE;
    /**
     * 默认 java.util.ArrayList
     */
    private Class<?> collectionClass = DEFAULT_COLLECTION_TYPE;

    @Override
    public void setParameterValues(Properties parameters) {
        String separatorValue = parameters.getProperty(SEPARATOR);
        if (separatorValue != null) {
            separator = separatorValue;
        }
        String elementType = parameters.getProperty(ELEMENT_TYPE);
        if (elementType != null) {
            this.elementClass = getClassType(elementType);
        }
        String collectionType = parameters.getProperty(COLLECTION_TYPE);
        if (collectionType != null) {
            Class<?> classType = getClassType(collectionType);
            if (!Collection.class.isAssignableFrom(classType)) {
                throw new IllegalArgumentException("[" + collectionType + "] should be subclass of [" + Collection.class.getName() + "]");
            }
            collectionClass = classType;
        }
    }

    private static Class<?> getClassType(String clazzType) {
        try {
            return Class.forName(clazzType);
        } catch (Exception e) {
            throw new IllegalStateException("Illegal class [" + clazzType + "]", e);
        }
    }

    @Override
    public int[] sqlTypes() {
        return new int[]{TextType.INSTANCE.sqlType()};
    }

    @Override
    public Class returnedClass() {
        return collectionClass;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return (x == y) || (x != null && x.equals(y));
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
        String valueStr = rs.getString(names[0]);
        if (StringUtils.isEmpty(valueStr)) {
            return null;
        }

        String[] values = StringUtils.split(valueStr, separator);

        Collection result = newCollection();

        for (String value : values) {
            result.add(ConvertUtils.convert(value, elementClass));
        }
        return result;
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
        String valueStr;
        if (value == null) {
            valueStr = "";
        } else {
            valueStr = StringUtils.join((Collection) value, separator);
        }
        st.setString(index, valueStr);
    }

    @Override
    public Object deepCopy(Object o) throws HibernateException {
        if (o == null) {
            return null;
        }
        Collection copyCollection = newCollection();
        copyCollection.addAll((Collection) o);
        return copyCollection;
    }

    private Collection newCollection() {
        try {
            return (Collection) collectionClass.newInstance();
        } catch (Exception e) {
            throw new HibernateException(e);
        }
    }

    @Override
    public boolean isMutable() {
        return true;
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return ((Serializable) value);
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        if (logger.isDebugEnabled()) {
            logger.debug("assemble owner[" + ToStringBuilder.reflectionToString(owner) + "]");
        }
        return deepCopy(cached);
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        if (logger.isDebugEnabled()) {
            logger.debug("replace owner[" + ToStringBuilder.reflectionToString(owner) + "]");
        }
        return deepCopy(original);
    }

}
